import { Module, RequestMethod } from '@nestjs/common';
import { CUSTOM_ROUTE_ARGS_METADATA, METHOD_METADATA, PARAMTYPES_METADATA, PATH_METADATA, ROUTE_ARGS_METADATA } from '@nestjs/common/constants';
import { DiscoveryService, MetadataScanner, NestContainer, Reflector } from '@nestjs/core';
import * as ts from 'typescript';
const fs = require('fs');
const path = require('path');

@Module({
	providers: [NestContainer, MetadataScanner, DiscoveryService]
})
export class GenerateSDKModule {

	srcPath=path.join(__dirname, "../src")

	constructor(
		private readonly discoveryService: DiscoveryService,
	) {}
	//get(GenerateSDKModule).discovery()
	discovery() {
		console.log("客户端SDK-TS生成开始")
		let controllers = this.discoveryService.getControllers();
		let arr = []
		for (let i = 0; i < controllers.length; i++) {
			let controller = controllers[i];
			let controllerObj = {
				className: controller.name,
				import: new Set(),
				deps:new Set(),
				routes: []
			}
			let controllerPath = Reflect.getMetadata(PATH_METADATA, controller.instance.constructor)
			if(controllerPath.startsWith("admin/")){
				var recursionFindDeps=(className)=>{
					let classContent = this.searchClass(this.srcPath,className)
					if(classContent.includes("extends")){
						let regex= /extends\s+(\w+)/
						let match = classContent.match(regex);
						let extractedString = match ? match[1] : '';
						if(extractedString!="PartialType"){
							controllerObj.deps.add(extractedString)
						}
					}
					let importClass=this.getMemberTypeFromAST(classContent)
					for (let m = 0; m < importClass.length; m++) {
						let deps = importClass[m];
						controllerObj.deps.add(deps)
						recursionFindDeps(deps)
					}
				}
				let methods = Object.getOwnPropertyNames(controller.instance.constructor.prototype);
				for (let method of methods) {
					if (method != "constructor" && typeof controller.instance[method] === 'function') {
						let shouldSdk=Reflect.getMetadata("generateSDK", controller.instance[method])
						if(!shouldSdk){
							continue;
						}
						let requestPath = path.posix.join(controllerPath, Reflect.getMetadata(PATH_METADATA, controller.instance[method]))
						if (requestPath.endsWith("/")) {
							requestPath = requestPath.slice(0, -1)
						}
						if (requestPath.includes(":")) {
							requestPath = requestPath.replace(/:([^/]+)/, '${$1}')
						}
						let requestMethod = RequestMethod[Reflect.getMetadata(METHOD_METADATA, controller.instance[method])]
						let requestName = method
						let route = {
							name: requestName,
							method: requestMethod,
							path: requestPath,
							pathParams: null,
							bodyParams: "",
							bodyType: null,
							returnType: "any",
							returnTypeClass: null,
							isList: false,
							desc: ""
						}
						let swagger = Reflect.getMetadata("swagger/apiOperation", controller.instance[method])
						route.desc = swagger?.summary
						let isList = Reflect.getMetadata("returnList", controller.instance[method])
						if (isList) {
							route.isList = isList
						}

						let bodyDecorator = Reflect.getMetadata(PARAMTYPES_METADATA, controller.instance, method)

						for (let k = 0; k < bodyDecorator.length; k++) {
							let func = bodyDecorator[k];
							if (func.name == "String") {
								let match = requestPath.match(/\${(.*?)}/);
								if (match) {
									route.pathParams = match[1] + ":string"
								}
							} else
								if (func.name.includes("Dto")) {
									controllerObj.import.add(func.name)
									route.bodyType = func.name
									route.bodyParams=func.name
								}
						}

						let returnType = Reflect.getMetadata("returnInterface", controller.instance[method])
						if (returnType) {
							route.returnType = returnType
							route.returnTypeClass = this.searchClass(this.srcPath, returnType)
							controllerObj.import.add(returnType)
						}
						controllerObj.routes.push(route)
					}
				}
				for (let i = 0; i < [...controllerObj.import].length; i++) {
					const element = [...controllerObj.import][i];

					let classContent = this.searchClass(this.srcPath,element)
					if(classContent.includes("extends")){
						let regex= /extends\s+(\w+)/
						let match = classContent.match(regex);
						let extractedString = match ? match[1] : '';
						if(extractedString!="PartialType"){
							controllerObj.deps.add(extractedString)
						}
					}
					let importClass=this.getMemberTypeFromAST(classContent)

					for (let m = 0; m < importClass.length; m++) {
						let deps = importClass[m];
						controllerObj.deps.add(deps)
						recursionFindDeps(deps)
					}
				}



				arr.push(controllerObj)
			}

		}
		this.writeFiles(arr)
	}

	searchClass(dir, pattern) {
		let res = ""
		let that=this
		function searchFiles(dir, pattern) {
			const files = fs.readdirSync(dir);
			for (const file of files) {
				const filePath = path.join(dir, file);
				const stat = fs.statSync(filePath);

				if (stat.isDirectory()) {
					searchFiles(filePath, pattern);
				} else if (stat.isFile() && file.endsWith('.ts')) {
					let content = fs.readFileSync(filePath, 'utf-8');
					let codeString=that.getCodeFromTsString(content,pattern)

					if (codeString) {
						codeString = codeString.replace(/(@ApiProperty\(\s*{[\s\S]*?\bdescription\b:\s*"([^"]*)"\s*[\s\S]*?}\))/g, '/**$2*/');
						codeString = codeString.replace(/@.*\([\s\S]*?\)\n*/gm, '');
						codeString = codeString.replace(/\)/gm, '');
						res=codeString
						// res=that.getCodeFromTsString(content,pattern)
					}
				}
			}
		}
		searchFiles(dir, pattern)
		res = res.replace(/@.*\n/gm, '');
		return res
	}

	writeFiles(arr) {
		let commonDeps=new Set()
		let commonDepsArr=[]
		for (let i = 0; i < arr.length; i++) {
			let group = arr[i];
			commonDepsArr.push(...group.deps)
		}
		commonDeps=new Set(commonDepsArr)

		let json=[]

		for (let i = 0; i < arr.length; i++) {
			let group = arr[i];
			for (let k = 0; k < [...group.import].length; k++) {
				let importName = [...group.import][k];
				if(commonDepsArr.includes(importName)){
					group.import.delete(importName)
				}
			}
			json.push(
				this.writeApiFile(group)
			)
			json.push(
				this.writeTypesFile(group)
			)
		}

		json.push(this.writeCommonTypesFile([...commonDeps]))
		let writePath = path.join(__dirname, "../static", "sdk.json")
		// 获取目录路径
		const directory = path.dirname(writePath);
		try {
			fs.mkdirSync(directory, { recursive: true });
			fs.writeFileSync(writePath, JSON.stringify(json))
			console.log("客户端SDK-TS生成完毕")
		} catch (error) {
			console.log(error)
		}
	}

	writeApiFile(group) {
		let template = ``
		let importLine = `import http from '#HttpFolder#http';\nimport type {#import#} from './types'`
		let startLine = `
		\nexport default class #ModuleName#Api {
		`

		let name = group.className.replace("Controller", "")

		startLine = startLine.replace("#ModuleName#", name)
		if (group.import.size > 0) {
			template = importLine.replace("#import#", [...group.import].join(" , ")) + startLine
		} else {
			template = startLine
		}
		for (let k = 0; k < group.routes.length; k++) {
			let route = group.routes[k];

			let params = ""
			if (route.pathParams) {
				params = route.pathParams
			}
			if (route.bodyParams) {
				if (params) {
					params = [params, `params:${route.bodyType}`].join(",")
				} else {
					params = [`params:${route.bodyType}`].join(",")
				}

			}

			let responseWapper="Axios.Response<#returnType#>"

			responseWapper=responseWapper.replace("#returnType#",route.returnType+(route.isList ? "[]" : ""))
			let paramsKey="params"
			if(route.method=="POST"){
				paramsKey="body"
			}
			template += `\n/**\n* ${route.desc}\n*/\nstatic ${route.name}(${params}): Promise<${responseWapper}> {
	return http.${route.method.toLowerCase()}(\`${route.path}\`,${params.includes("params") ? route.method.toLowerCase()=='get'?'{params:params}':'params':'{}'});
}`
		}
		template += "\n}"

		return {
			folder:name,
			fileName:name+".ts",
			content:template
		}
	}

	writeTypesFile(group) {
		let importLine = `import { #import# } from '../Common/types'`
		let template = ``
		if(group.deps.size>0){
			template=importLine.replace("#import#", [...group.deps].join(" , "))
		}
		for (let k = 0; k < [...group.import].length; k++) {
			let importName = [...group.import][k];
			let classContent = this.searchClass(this.srcPath, importName)
			template += '\n' + classContent
		}
		let name = group.className.replace("Controller", "")

		return {
			folder:name,
			fileName:"types.ts",
			content:template
		}
	}

	writeCommonTypesFile(arr:any[]){
		let template = ``
		for (let i = 0; i < arr.length; i++) {
			let importName = arr[i];
			let classContent = this.searchClass(this.srcPath, importName)
			template += '\n' + classContent
		}

		return {
			folder:"Common",
			fileName:"types.ts",
			content:template
		}
	}

	getMemberTypeFromAST(code) {
		let importClass=[]
		const sourceFile = ts.createSourceFile(
			'file.ts', // 文件名
			code,      // 文件内容
			ts.ScriptTarget.Latest, // 目标 ECMAScript 版本
			true       // 是否设置为严格模式
		);
		// 遍历 AST（抽象语法树）
		ts.forEachChild(sourceFile, (node) => {
			if (ts.isClassDeclaration(node)||ts.isInterfaceDeclaration(node)) {
				node.members.forEach((member) => {
					let propertyName = member.name.getText(sourceFile);
					let propertyType = member.type?.getText(sourceFile);
					if(propertyType==undefined){
						// console.log(`属性名undefined: ${propertyName}`);
						propertyType=propertyName
					}else{
						propertyType=propertyType.replace("[]","")
						if(propertyType!="string"&&propertyType!="number"&&propertyType!="string | number"&&propertyType!="boolean"&&propertyType!="Date"&&propertyType!="any"&&!propertyType.startsWith("{")){
							importClass.push(propertyType)
						}
					}
				});
			}
		});
		return importClass
	}

	getCodeFromTsString(code:string,findName:string){
		// 解析代码
		let sourceFile = ts.createSourceFile('test.ts', code, ts.ScriptTarget.ESNext, true);
		let printer=ts.createPrinter()
		let codeString=""
		// 遍历 AST，找到所有的 class 和 interface
		ts.forEachChild(sourceFile, node => {
			if(ts.isClassDeclaration(node)||ts.isInterfaceDeclaration(node)||ts.isEnumDeclaration(node)){
				if(node.name.text==findName){
					codeString=printer.printNode(ts.EmitHint.Unspecified, node, sourceFile)
				}
			}
		});
		return codeString
	}
}

